﻿using System.Collections.Generic;
using System.Xml.Linq;

namespace Comdiv.QWeb.Serialization.BxlParser {
	public class BxlXmlGenerator {
		public XElement Generate(IEnumerable<BxlToken> tokens, string filename = "", bool lexdata = true,
		                         bool safeanonymnames = false, bool codeonlyattribute = false) {
			var ns = new Dictionary<string, string>();
			var _code = "code";
			var _id = "id";
			var _name = "name";
			if (safeanonymnames) {
				_code = "__code";
				_id = "__id";
				_name = "__name";
			}
			var root = new XElement("root");
			var current = root;
			var level = 0;
			var anonymcount = 0;
			foreach (var t in tokens) {
				var tp = t.Type;
				if (BxlTokenType.Start == tp) continue;
				if (BxlTokenType.Element == tp) {
					var e = new XElement(t.GetAdaptedName(ns));
					if (lexdata) {
						if (!string.IsNullOrWhiteSpace(filename)) {
							e.SetAttributeValue("_file", filename);
						}
						e.SetAttributeValue("_line", t.LexInfo.Line);
					}
					anonymcount = 0;
					var l = 0;
					if (t.Prev.Type == BxlTokenType.Level) {
						l = (int) t.Prev.Number;
					}
					if (l == level + 1 || current == root) {
						current.Add(e);
					}
					else if (l == level) {
						current.Parent.Add(e);
					}
					else if (l < level) {
						for (var i = 0; i < level - l; i++) {
							current = current.Parent;
						}
						current.Parent.Add(e);
					}
					else {
						throw new BxlParserException("invalid element levelshift " + t.LexInfo, t.LexInfo);
					}
					level = l;
					current = e;
					continue;
				}
				if (BxlTokenType.Value == tp) {
					current.Value = t.Value;
					continue;
				}
				if (BxlTokenType.Attribute == tp) {
					var l = 0;
					if (t.Prev.Type == BxlTokenType.Level) {
						l = (int) t.Prev.Number;
					}
					else {
						if (t.Prev.Type != BxlTokenType.NewLine && t.Prev.Type != BxlTokenType.Start) {
							l = -1;
						}
					}

					if (l > level || l == -1) {
						current.SetAttributeValue(t.GetAdaptedName(ns), t.AttrValue);
					}
					else if (l == 0) {
						//level 0 means as namespace declaration if no elements above
						if (current.Parent == null) //root
						{
							ns[t.Name] = t.AttrValue;
							current.Add(new XAttribute(XNamespace.Xmlns + t.Name, t.AttrValue));
						}
						else
							throw new BxlParserException("insuficient attribute level 0" + t.LexInfo, t.LexInfo);
					}
					else if (l == level) {
						current.Parent.SetAttributeValue(t.GetAdaptedName(ns), t.AttrValue);
					}
					else if (l < level && l != 0) {
						for (var i = l; i <= level; i++) {
							if (current.Parent == null) {
								throw new BxlParserException("insuficient attribute level" + t.LexInfo, t.LexInfo);
							}
							current = current.Parent;
						}
						level = l - 1;
						current.SetAttributeValue(t.GetAdaptedName(ns), t.AttrValue);
					}

					continue;
				}
				if (BxlTokenType.String == tp || BxlTokenType.Expression == tp) {
					if (anonymcount > 2) {
						anonymcount++;
						current.SetAttributeValue("_aa" + anonymcount, t.Value);
					}
					if (anonymcount == 0) {
						anonymcount++;
						anonymcount++;
						current.SetAttributeValue(_code, t.Value);
						if (!codeonlyattribute) {
							current.SetAttributeValue(_id, t.Value);
						}
					}
					else if (anonymcount == 2) {
						anonymcount++;
						current.SetAttributeValue(_name, t.Value);
					}
					continue;
				}
				if (BxlTokenType.Literal == tp) {
					if (anonymcount == 0) {
						anonymcount++;
						anonymcount++;
						current.SetAttributeValue(_code, t.Value);
						if (!codeonlyattribute) {
							current.SetAttributeValue(_id, t.Value);
						}
					}
					else if (anonymcount == 2) {
						anonymcount++;
						current.SetAttributeValue(_name, t.Value);
					}
					else {
						anonymcount++;
						current.SetAttributeValue(BxlToken.ToXmlName(t.Value), "1");
					}
					continue;
				}
			}
			return root;
		}
	}
}